namespace TwitterButtler.RequestServer.WakeOnLan
{
	#region #using Directives

	using System;
	using System.Collections.Generic;
	using System.Globalization;
	using System.IO;
	using System.Linq;
	using System.Net.NetworkInformation;
	using System.Reflection;
	using System.Text;
	using System.Windows.Threading;

	#endregion

	public abstract class MachinePowerStatusButtlerRequestServerBase : IDisposable
	{
		private readonly FileSystemWatcher fileSystemWatcher;
		protected readonly Dictionary<string, WakeOnLanUserEntry> userNameToMacAddressDictionary = new Dictionary<string, WakeOnLanUserEntry>();
		protected Dispatcher dispatcher;

		protected MachinePowerStatusButtlerRequestServerBase()
		{
			this.dispatcher = Dispatcher.CurrentDispatcher;

			string folderName = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
			this.fileSystemWatcher = new FileSystemWatcher(folderName, "WakeOnLanUsers.cfg")
			                         	{
			                         		EnableRaisingEvents = true
			                         	};
			this.fileSystemWatcher.Created += OnConfigFileChanged;
			this.fileSystemWatcher.Changed += OnConfigFileChanged;

			ReadConfigFile();
		}

		private void ReadConfigFile()
		{
			string filePath = Path.Combine(this.fileSystemWatcher.Path, this.fileSystemWatcher.Filter);
			if (File.Exists(filePath))
			{
				this.userNameToMacAddressDictionary.Clear();

				using (var streamReader = new StreamReader(filePath))
				{
					for (;;)
					{
						string line = streamReader.ReadLine();
						if (line == null)
						{
							break;
						}

						var tokens = line.Split(new[] {',', ';'});
						string twitterUserName = null;
						string macAddress = null;
						string machineName = null;

						switch (tokens.Length)
						{
							case 2:
								twitterUserName = tokens.First().Trim();
								macAddress = tokens.Last().Trim();
								break;
							case 3:
								twitterUserName = tokens.First();
								macAddress = tokens[1].Trim();
								machineName = tokens.Last().Trim();
								break;
						}

						if (!string.IsNullOrEmpty(twitterUserName))
						{
							this.userNameToMacAddressDictionary[twitterUserName] = new WakeOnLanUserEntry
							                                                       	{
							                                                       		UserName = twitterUserName,
							                                                       		MachineName = machineName,
							                                                       		MachineMacAddress = GetMacAddress(macAddress)
							                                                       	};
						}
					}
				}
			}
		}

		private void OnConfigFileChanged(object sender, FileSystemEventArgs e)
		{
			this.dispatcher.BeginInvoke((Action) ReadConfigFile);
		}

		protected static bool Ping(WakeOnLanUserEntry entry, int retries)
		{
			var pingSender = new Ping();
			var options = new PingOptions
			              	{
			              		DontFragment = true
			              	};

			// Use the default Ttl value which is 128,
			// but change the fragmentation behavior.

			// Create a buffer of 32 bytes of data to be transmitted.
			const string data = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa";
			var timeOut = TimeSpan.FromSeconds(1.0);
			var buffer = Encoding.ASCII.GetBytes(data);
			for (int retry = 0; retry <= retries; ++retry)
			{
				try
				{
					var reply = pingSender.Send(entry.MachineName, (int) timeOut.TotalMilliseconds, buffer, options);
					if (reply.Status == IPStatus.Success)
					{
						return true;
					}
				}
				catch (PingException)
				{
					if (retry == retries)
					{
						throw;
					}
				}
			}

			return false;
		}

		protected static void SendReply(string screenName, DateTime originalRequestDateTime, bool successful, string requestText,
		                                Action<string, string> onRequestCompleted)
		{
			string replyMessage =
				string.Format(successful ? "The '{1}' request you sent on {0} performed successfully." : "The '{1}' request you sent on {0} failed to perform.",
				              originalRequestDateTime, requestText);
			onRequestCompleted(screenName, replyMessage);
		}

		private static byte[] GetMacAddress(string macAddress)
		{
			var tokens = macAddress.Split(new[] {'-'});

			if (tokens.Length == 6)
			{
				var macAddressByteList = new List<byte>(tokens.Length);
				foreach (string token in tokens)
				{
					macAddressByteList.Add(byte.Parse(token, NumberStyles.HexNumber));
				}

				return macAddressByteList.ToArray();
			}

			throw new ArgumentException(string.Format("The MAC address must contain 6 bytes separated by dashes. '{0}' is not a valid MAC address.", macAddress));
		}

		#region Implementation of IDisposable

		/// <summary>
		/// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
		/// </summary>
		/// <filterpriority>2</filterpriority>
		public void Dispose()
		{
			Dispose(true);
			GC.SuppressFinalize(this);
		}

		protected virtual void Dispose(bool disposing)
		{
			if (disposing)
			{
				this.fileSystemWatcher.Created -= OnConfigFileChanged;
				this.fileSystemWatcher.Changed -= OnConfigFileChanged;
				this.fileSystemWatcher.Dispose();
			}
		}

		#endregion
	}

	internal class PingParams
	{
		public PingParams(WakeOnLanUserEntry entry, string requestText, DateTime originalRequestDateTime, Action<string, string> onRequestCompleted)
		{
			Entry = entry;
			RequestText = requestText;
			OriginalRequestDateTime = originalRequestDateTime;
			OnRequestCompleted = onRequestCompleted;
		}

		public WakeOnLanUserEntry Entry { get; private set; }
		public string RequestText { get; private set; }
		public DateTime OriginalRequestDateTime { get; private set; }
		public Action<string, string> OnRequestCompleted { get; private set; }
	}

	public class WakeOnLanUserEntry
	{
		public string UserName { get; set; }
		public string MachineName { get; set; }
		public byte[] MachineMacAddress { get; set; }
	}
}